import { Cascader as AntCascader, CascaderProps } from 'antd';
import { CascaderRef } from 'antd/es/cascader';
import React, { useEffect, useRef, useState } from 'react';
import { compHoc, registerComponent, iup, useExtendRef, useRefCallback } from '../../util';

interface AreaData {
  creator: string;
  dataName: string;
  dataNo: string;
  deleted: string;
  editor: string;
  id: string;
  insertDt: string;
  mainDataCode: string;
  mainDataFlg: number;
  recordVer: number;
  seq: number;
  status: number;
  sysSdSearchKey: number;
  sysShareFlg: number;
  tenantId: number;
  treeId: number;
  treePid: string;
  typeCode: string;
  updateDt: string;
  userModFlg: number;
}
type AntCascaderOption = {
  value: AreaData['dataName'];
  label: AreaData['dataName'];
  children: AntCascaderOption[];
  disabled?: boolean;
  isLeaf?: boolean;
  origin: AreaData;
};
type PickCascaderProps =
  | 'allowClear'
  | 'autoFocus'
  | 'changeOnSelect'
  | 'className'
  | 'disabled'
  | 'expandTrigger'
  | 'size'
  | 'style'
  | 'onDropdownVisibleChange';

export interface AreaLinkageProps extends Pick<CascaderProps<AntCascaderOption>, PickCascaderProps> {
  id?: string;
  country?: string; // country code default 'CN'
  fitHeight?: boolean;
  value?: string[];
  defaultValue?: string[];
  onChange?: (values: string[], options: AntCascaderOption[]) => void;
}

const useAreaLinkageOptions = (country: string) => {
  const [options, setOptions] = useState<AntCascaderOption[]>([]);
  //TODO use location cache?
  const areaCacheRef = useRef<Record<string, boolean>>({});
  const loadCountryPromise = useRef<Promise<any>>();

  useEffect(() => {
    if (!areaCacheRef.current[country]) {
      loadCountryPromise.current = fetchOptions(country.toUpperCase()).then((res) => {
        areaCacheRef.current = {
          [country]: true
        };
        const _options = transformOptions(res);
        loadCountryPromise.current = undefined;
        setOptions(_options);
        return _options;
      });
    }
  }, [country]);

  const loadData = useRefCallback(async (selectedValues: string[]) => {
    if (selectedValues.length === 0) {
      return;
    }
    if (selectedValues.every((value) => areaCacheRef.current[value])) {
      return;
    }
    let _options = options;
    if (loadCountryPromise.current) {
      _options = await Promise.resolve(loadCountryPromise.current);
    }
    let currentOptions = _options;
    for await (const value of selectedValues) {
      if (!areaCacheRef.current[value]) {
        const pid = currentOptions.find((v) => v.value === value)?.origin.dataNo;
        if (pid) {
          const res = await fetchOptions(pid);
          const isLeaf = selectedValues[1] === value; // 目前为 3 级结构，第三级即为叶子节点
          const nextOptions = transformOptions(res, isLeaf);
          currentOptions?.forEach((_option, i) => {
            if (_option.value === value) {
              currentOptions[i] = {
                ..._option,
                children: nextOptions
              };
            }
          });
          areaCacheRef.current[value] = true;
        }
      }
      currentOptions = currentOptions?.find((v) => v.value === value)?.children || [];
    }
    setOptions(iup.deepCopyEx(_options));
  });

  return {
    options,
    loadData
  };
};

const defaultProps: CascaderProps = {
  allowClear: true,
  placeholder: '请选择'
};
const AreaLinkage: React.FC<AreaLinkageProps & { observer: any; outRef: any }> = ({
  country = 'CN',
  outRef,
  observer,
  value,
  defaultValue,
  onChange,
  onDropdownVisibleChange,
  ...rest
}) => {
  const cascaderRef = useRef<CascaderRef>(null);
  const [open, setOpen] = useState(false);
  const [loading, setLoading] = useState(false);
  const [innerValue, setInnerValue] = useState<string[]>(value || defaultValue || []);
  const { options, loadData } = useAreaLinkageOptions(country.toUpperCase());

  useExtendRef(outRef, {
    getValue() {
      return innerValue;
    },
    setValue(v: string[]) {
      if (value === v || innerValue === v) {
        return;
      }
      setInnerValue(v);
      onChange?.(v, []);
    },
    focus: () => cascaderRef.current?.focus(),
    blur: () => cascaderRef.current?.blur()
  });

  return (
    <AntCascader<AntCascaderOption>
      {...defaultProps}
      {...rest}
      open={open}
      loading={loading}
      value={value || innerValue}
      onDropdownVisibleChange={async (open) => {
        if (open) {
          setLoading(true);
          // check value options is loaded
          await loadData(value || innerValue);
          setLoading(false);
        }
        setOpen(open);
        onDropdownVisibleChange && onDropdownVisibleChange(open);
      }}
      onChange={(values, selectedOption) => {
        setInnerValue(values as string[]);
        onChange && onChange(values as string[], selectedOption);
      }}
      loadData={(options) => loadData(options.map((v) => v.value))}
      options={options}
      ref={cascaderRef}
    />
  );
};
const AreaLinkageWithHoc = compHoc(AreaLinkage, 'AreaLinkage');
export { AreaLinkageWithHoc as AreaLinkage };

function transformOptions(data: AreaData[], isLeaf = false) {
  return data.map((item) => {
    return {
      value: item.dataName,
      label: item.dataName,
      children: [],
      disabled: false,
      isLeaf,
      origin: item
    } as AntCascaderOption;
  });
}
async function fetchOptions(pid: string) {
  try {
    const { code, data, message } = await iup.request.get({
      url: `engine/simpleData/data/getSubSimpleDataList`,
      data: {
        typeCode: 'area',
        treePid: pid
      }
    });
    if (code === 0) {
      return data as AreaData[];
    }
    iup.alert(message);
    return [];
  } catch (e: any) {
    iup.alert(e.message);
    return [];
  }
}

registerComponent({ AreaLinkage });
